﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using JtextEpicsInterface;
using string_short;
using jtext_EpicsClient;
using jtext_EpicsServer;
using EpicsJecketClient;
using EpicsJecket;
using PSIEpics;
using System.Threading;


namespace LibAutoTransmeter
{
    /// <summary>
    /// this automaticlly trans cmd vie ejServer
    /// THIS　IS VERSION 2.0
    /// </summary>
    public class Transmeter
    {
        //the pvname prefix in template 
        const string PV_PREFIX = "PV_";
        //themplate spliter
        const string TEMP_SPLITER = ";";
        const int CHECKINTERVAL = 1500;

        public bool isConsole;
        public TranmeterProp myProp;
        public EJServer myEJServer;
        public EJClient myEJClient;
        public JECLient myJEClient;
        public JEpicsServer myJEServer;
        public  delegate void FlagChangeHandler(object val);
        public event FlagChangeHandler OnFlagChange;

        FlagChangeHandler myFlagChange;
        Thread threadCheck;
        public Transmeter(TranmeterProp Prop)
        {
            myProp = Prop;
            isConsole = true;
            
        }


        public void  IniTransmeter()
        {
            //create monitor and if host create pv record.
            //for now the if host is not used
            myJEClient = new JECLient();
            //used event instead if brings bug in uncomment below 
            myFlagChange = new FlagChangeHandler(DoFlagChanged);
            //used event instead if brings bug in comment below 
            //OnFlagChange += DoFlagChanged;

            foreach (DictionaryEntry tPvName in myProp.Values)
            {
                //myJEClient.AddChannel<ExtTimeType<string>>((string)(tPvName.Key));
                myJEClient.AddChannel((string)(tPvName.Key));
                Console.WriteLine("adding pv for transmeter: "+tPvName.Key);
            }
            Thread.Sleep(100);

            if (myProp.noMonitor == false)
            {
                myJEClient[myProp.PvFlag].MonitorMask = MonitorMask.VALUE;
                //simpleify the monitor type
                //((EpicsChannel<ExtTimeType<string>>)myJEClient[myProp.PvFlag]).MonitorChanged += new EpicsDelegate<ExtTimeType<string>>(FlagChanged);
                ((EpicsChannel)myJEClient[myProp.PvFlag]).MonitorChanged += new EpicsDelegate(FlagChanged);
                Console.WriteLine(myProp.PvFlag);
            }
            else
            {
                threadCheck = new Thread(new ThreadStart(checkFlag));
                threadCheck.Start();
            }

            

        }

        private void checkFlag()
        {
            string lastFlag="";
            try
            {
                lastFlag = myJEClient.RecordVAL[myProp.PvFlag].ToString();
                Console.WriteLine("LastFlag: " + lastFlag.ToString());
            }
            catch
            {
                Console.WriteLine("flag unknow");
            }
            Random tRnd = new Random();
            while (true)
            {
                Thread.Sleep(CHECKINTERVAL+tRnd.Next(20,500));
                if (myProp.noStop == true)
                {
                    try
                    {
                        string tFlag=myJEClient.RecordVAL[myProp.PvFlag].ToString();
                        lastFlag = tFlag;
                        //myFlagChange.BeginInvoke(tFlag, null, null);
                        DoFlagChanged(tFlag);
                        if (OnFlagChange != null)
                        {
                            OnFlagChange(tFlag);
                        }
                    }
                    catch
                    {
                        Console.WriteLine("flag unknow"); 
                    }
                }
                else
                {
                    string tFlag;
                    try
                    {
                        tFlag = myJEClient.RecordVAL[myProp.PvFlag].ToString();
                    }
                    catch
                    {
                        Console.WriteLine("flag unknow"); 
                        continue;
                    }
                    if (lastFlag == tFlag)
                    {
                        continue;
                    }
                    else
                    {
                        lastFlag = tFlag;
                        //myFlagChange.BeginInvoke(tFlag, null, null);
                        DoFlagChanged(tFlag);
                        if (OnFlagChange != null)
                        {
                            OnFlagChange(tFlag);
                        }
                    }
                }
                
            }//while

        }
        
        /// <summary>
        /// Flags the changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="newValue">The new value.</param>
        private void FlagChanged(EpicsChannel sender, object newValue)
        {
            Console.WriteLine("Flag Changed !!!!!!!!!!!!!!!!!!!!!: " +newValue.ToString());
            //used event instead if brings bug in uncomment below 


            myFlagChange.BeginInvoke(newValue, null, null);

            if (OnFlagChange != null)
            {
                OnFlagChange(newValue);
            }

        }

        /// <summary>
        /// Does the job when flag changed.in another thread
        /// </summary>
        /// <param name="newValue">The new value.</param>
        private void DoFlagChanged(object newValue)
        {
            //Console.WriteLine("Do change");
            Thread.Sleep(100);
            if (myProp.revision == 1)
            {
                if (checkDebug() == true)
                {
                    return;
                }
                if (checkMask(newValue,myProp.mask,myProp.noRegex) == false)
                {
                    return;
                }
                doTransmiter(myProp.myTemplate);
            }
            else if (myProp.revision == 2)
            {
                if (checkDebug() == true)
                {
                    
                    return;
                }
                //in revision 2 there multiple commands in one transmiter
                foreach (TransCommand tCmd in myProp.commands)
                {
                    if (checkMask(newValue, tCmd.mask, tCmd.noRegex) == true)
                    {
                        Console.WriteLine("Sending Command: " + tCmd.name + "with Template:" + tCmd.template);
                        doTransmiter(tCmd.template);
                    }
                }
            }
            
            //Console.WriteLine("finish change");
        }

        /// <summary>
        /// if the outside want to Does the transmiter, it can force it to do one.
        /// </summary>
        public void doTransmiter(string tTemplate)
        {
            object message;
            message = makeMessage(tTemplate);
            if (message == null)
            {
                ConsoleOut("Can not get message PV");
                return;
            }
            if (myProp.isByte == true)
            {
                byte[] byteMsg = (byte[])message;
                SendMessage(byteMsg);
                string PrtMsg = Str_Short.BytesToHexString(byteMsg, ";");
                ConsoleOut(PrtMsg);
            }
            else
            {
                string strMsg = (string)message;
                Console.WriteLine(strMsg);
                SendMessage(strMsg);
                
            }
        }


        /// <summary>
        /// Checks if the flag change is filted by mask, also check if disabled
        /// </summary>
        /// <returns></returns>
        private bool checkMask(object newValue,string tMask,bool noRegex)
        {
            //string flagVal = ((ExtTimeType<string>)newValue).Value.ToString();
            string flagVal = newValue.ToString();
            if (noRegex == false)
            {
                if (Regex.IsMatch(flagVal, tMask))
                {
                    return true;    //aproved
                }
                Console.WriteLine("Masked: " + flagVal);
                return false;   //masked
            }
            else
            {
                //do not used regex match
                if (flagVal == tMask)
                {
                    return true;    //aproved
                }
                Console.WriteLine("Masked(noRegex): " + flagVal);
                return false;   //masked
            }
        }

        /// <summary>
        /// Checks if in debug mode.
        /// </summary>
        /// <returns></returns>
        private bool checkDebug()
        {
            if (myProp.PvDisable != "null")
            {
                //get debug pv
                int debugVal;
                try
                {
                    debugVal = (int)(myJEServer[myProp.PvDisable].PVAL);
                }
                catch
                {
                    debugVal=Convert.ToInt32(myJEClient.RecordVAL[myProp.PvDisable]);
                }
                if ( debugVal!= 0)
                {
                    Console.WriteLine("Not in debug: " + myProp.PvDisable);
                    return true;
                }
            }
            //ignore debug
            return false;
        }

        /// <summary>
        /// Makes the message.
        /// </summary>
        /// <returns></returns>
        public object makeMessage(string tTemplate)
        {
            //try
            //{
                //Random ranSleep = new Random();
                //Thread.Sleep(10 + ranSleep.Next(10, 100));
                if (myProp.isByte == true)
                {
                    return makeByteMsg(tTemplate);
                }
                else
                {
                    return makeStrMsg2(tTemplate);
                }
            //}
            //catch (Exception e)
            //{
            //    Console.WriteLine(e.Message);
            //    return null;
            //}
            
        }

        /// <summary>
        /// Makes the byte message.
        /// the different speicifies the lenth of the value in the message,it has nothing to do with the data type of the pv-data_type
        /// </summary>
        /// <returns></returns>
        private byte[] makeByteMsg(string tTemplate)
        {
            ArrayList msg = new ArrayList();
            string[] msgArray = Regex.Split(tTemplate, TEMP_SPLITER);
            for (int i = 0; i < msgArray.Length; i++)
            {
                
                string[] tPvName=Regex.Split(msgArray[i],PV_PREFIX);
                byte msgElemten=0;
                if (tPvName.Length != 2)
                {
                    msgElemten = Convert.ToByte(msgArray[i], 16);
                    msg.Add(msgElemten);
                    continue;
                }
                if (myProp.Values.Contains(tPvName[1]))
                {
                    //converter
                    
                    switch ((string)(myProp.Values[tPvName[1]]))
                    {
                        case CommonConst.DT_BYTE: 
                            {
                                string thisVal = myJEClient.RecordVAL[tPvName[1]].ToString();
                                sbyte sb=Convert.ToSByte(thisVal,10);
                                byte b  = (byte)sb;
                                msg.Add(b);
                                break;
                            }
                        case CommonConst.DT_BYTEARRAY:
                            {
                                byte[] b;//b = (byte[])myJEClient.RecordVAL[tPvName[1]];
                                try
                                {
                                    b = ((byte[])(myJEServer[((string)(tPvName[1]))].PVAL));
                                }
                                catch
                                {
                                    b = (byte[])myJEClient.RecordVAL[tPvName[1]];
                                }
                                msg.AddRange(b);
                                break;
                            }
                        case CommonConst.DT_JWAVE:
                            {
                                byte[] a = (byte[])myJEClient.RecordVAL[tPvName[1]];
                                //byte[] byteLenth=new byte[2] ;
                                //byteLenth[0]=a[0];
                                //byteLenth[1]=a[1];
                                Int16 lenth = BitConverter.ToInt16(a, 0);
                                byte[] b = new byte[lenth];
                                ConsoleOut("lenth: " + lenth.ToString());
                                int j;
                                for (j = 0; j < lenth; j++)
                                {
                                    b[j] = a[j + 2];
                                }
                                msg.AddRange(b);
                                break;
                            }
                        case CommonConst.DT_INT16:
                            {
                                string thisVal = myJEClient.RecordVAL[tPvName[1]].ToString();
                                Int16 sb=Convert.ToInt16(thisVal);
                                byte[] b = BitConverter.GetBytes(sb);
                                msg.AddRange(b);
                                break;
                            }
                        case CommonConst.DT_INT:
                            {
                                string thisVal = myJEClient.RecordVAL[tPvName[1]].ToString();
                                Int32 sb = Convert.ToInt32(thisVal);
                                byte[] b = BitConverter.GetBytes(sb);
                                msg.AddRange(b);
                                break;
                            }
                        case CommonConst.DT_INT64:
                            {
                                string thisVal = myJEClient.RecordVAL[tPvName[1]].ToString();
                                Int64 sb = Convert.ToInt64(thisVal);
                                byte[] b = BitConverter.GetBytes(sb);
                                msg.AddRange(b);
                                break;
                            }

                        default:
                            {
                                break;
                            }
                    }
                }
                else
                {
                    msgElemten = Convert.ToByte(msgArray[i], 16);
                    msg.Add(msgElemten);
                }
                
            }
            byte[] realMsg = (byte[])(msg.ToArray(typeof(byte)));
            return realMsg;
        }

        private string makeStrMsg2(string tTemplate)
        {
            string msg;
            msg = tTemplate;
            string[] msgArray = Regex.Split(msg, TEMP_SPLITER);
            for (int i = 0; i < msgArray.Length; i++)
            {

                string[] tPvName = Regex.Split(msgArray[i], PV_PREFIX);
                
                if (tPvName.Length != 2)
                {
                    continue;
                }
                if (myProp.Values.Contains(tPvName[1]))
                {
                    //converter
                    string tString;
                    string[] dataType = Regex.Split( ((string)(myProp.Values[tPvName[1]])) , TEMP_SPLITER);
                    string strFomat = "";
                    if (dataType.Length > 1)
                    {
                        strFomat = dataType[1];
                    }
                    switch (dataType[0])
                    {
                        case CommonConst.DT_BYTE:
                            {
                                string s;
                                //if (myJEServer == null)
                                //{
                                //    s = myJEClient.RecordVAL[(string)(tPvName[1])].ToString();
                                //}
                                //else
                                //{
                                //    //use values on the server
                                //    //s = ((double[])(myJEServer[((string)(tPvName.Key))].VAL));
                                //    //use pv on server
                                //    s = (myJEServer[((string)(tPvName[1]))].PVAL).ToString();
                                //}
                                try
                                {
                                    s = (myJEServer[((string)(tPvName[1]))].PVAL).ToString();
                                }
                                catch
                                {
                                    s = myJEClient.RecordVAL[(string)(tPvName[1])].ToString();
                                }

                                sbyte sb = Convert.ToSByte(s, 10);
                                byte b = (byte)sb;
                                tString = b.ToString("X2");
                                break;
                            }
                        case CommonConst.DT_STRING:
                            {
                                tString = myJEClient.GetString((string)(tPvName[1]));
                                break;
                            }
                        case CommonConst.DT_JWAVE:
                            {
                                Console.WriteLine("Try Get PV: " + (string)(tPvName[1]));
                                //double[] s = (double [])(myJEClient.RecordVAL[(string)(tPvName.Key)]);   //join all the double values to a string, spliter is ";"
                                double[] s;
                                //if (myJEServer == null)
                                //{
                                //    s = myJEClient[(string)(tPvName[1])].Get<double[]>(110);
                                //}
                                //else
                                //{
                                //    //use values on the server
                                //    //s = ((double[])(myJEServer[((string)(tPvName.Key))].VAL));
                                //    //use pv on server
                                //    s = ((double[])(myJEServer[((string)(tPvName[1]))].PVAL));
                                //}
                                try
                                {
                                    s = ((double[])(myJEServer[((string)(tPvName[1]))].PVAL));
                                }
                                catch
                                {
                                    s = myJEClient[(string)(tPvName[1])].Get<double[]>(110);
                                }
                                double iwaveLen = s[0];
                                Console.WriteLine("Fill PV: " + (string)(tPvName[1]));
                                Console.WriteLine("PV Lenth : " + s.Length.ToString());
                                Console.WriteLine("WAVE LENTH: " + iwaveLen.ToString());
                                if (strFomat != "")
                                {
                                    Console.WriteLine("With Format: " + strFomat);
                                    tString = s[1].ToString(strFomat);
                                    Console.WriteLine(s[1].ToString(strFomat));
                                    for (int j = 2; j <= iwaveLen; j++)
                                    {
                                        tString += TEMP_SPLITER + s[j].ToString(strFomat);
                                        Console.WriteLine(s[j].ToString(strFomat));
                                    }
                                }
                                else
                                {
                                    tString = s[1].ToString();
                                    Console.WriteLine(s[1].ToString());
                                    for (int k = 2; k <= iwaveLen; k++)
                                    {
                                        tString += TEMP_SPLITER + s[k].ToString();
                                        Console.WriteLine(s[k].ToString());
                                    }
                                }
                                break;
                            }

                        default:
                            {
                                if (strFomat != "")
                                {

                                    Console.WriteLine("Write with Format: " + strFomat);
                                    double s;
                                    //if (myJEServer == null)
                                    //{
                                    //    s = (double)myJEClient.RecordVAL[(string)(tPvName[1])];
                                    //}
                                    //else
                                    //{
                                    //    //use values on the server
                                    //    //s = ((double[])(myJEServer[((string)(tPvName.Key))].VAL));
                                    //    //use pv on server
                                    //    s = ((double)(myJEServer[((string)(tPvName[1]))].PVAL));
                                    //}
                                    try
                                    {
                                        s = ((double)(myJEServer[((string)(tPvName[1]))].PVAL));
                                    }
                                    catch
                                    {
                                        s = (double)myJEClient.RecordVAL[(string)(tPvName[1])];
                                    }
                                    tString = s.ToString(strFomat);

                                }
                                else
                                {
                                    //if (myJEServer == null)
                                    //{
                                    //    tString = myJEClient.RecordVAL[(string)(tPvName[1])].ToString();
                                    //}
                                    //else
                                    //{
                                    //    //use values on the server
                                    //    //s = ((double[])(myJEServer[((string)(tPvName.Key))].VAL));
                                    //    //use pv on server
                                    //    tString = ((myJEServer[((string)(tPvName[1]))].PVAL)).ToString();
                                    //    Console.WriteLine("fill: " + tPvName[1].ToString() + " = " + tString);
                                    //}
                                    try
                                    {
                                        tString = ((myJEServer[((string)(tPvName[1]))].PVAL)).ToString();
                                    }
                                    catch
                                    {
                                        tString = myJEClient.RecordVAL[(string)(tPvName[1])].ToString();
                                    }

                                    //tString = (myJEServer[((string)(tPvName.Key))].PVAL).ToString();
                                }
                                break;
                            }
                    }//switch
                    msgArray[i] = tString;
                }//for

            }
            msg = string.Join(TEMP_SPLITER, msgArray);
            return msg;
        }
        
        
        
        /// <summary>
        /// Makes the string message.
        /// only byte is using hex fomat,other is using decimal format
        /// </summary>
        /// <returns></returns>
        private string makeStrMsg(string tTemplate)
        {
            string msg;
            string[] msgArray;
            msg = tTemplate;

            msgArray = Regex.Split(msg, TEMP_SPLITER);
            Console.WriteLine("start making message");
            foreach (DictionaryEntry tPvName in myProp.Values)
            {
                string tString;
                string[] dataType = Regex.Split((string)tPvName.Value, TEMP_SPLITER);
                string strFomat="";
                if (dataType.Length > 1)
                {
                    strFomat = dataType[1];
                }
                Console.WriteLine("Fill PV: " + (string)(tPvName.Key) + "  DType= " + dataType[0]);
                if ((string)(dataType[0]) == CommonConst.DT_STRING)   //get string from the pv
                {
                    tString = myJEClient.GetString((string)(tPvName.Key));
                }
                else if ((string)(dataType[0]) == CommonConst.DT_BYTE)    //use hex string
                {
                    string s;
                    if (myJEServer == null)
                    {
                        s = myJEClient.RecordVAL[(string)(tPvName.Key)].ToString();
                    }
                    else
                    {
                        //use values on the server
                        //s = ((double[])(myJEServer[((string)(tPvName.Key))].VAL));
                        //use pv on server
                        s = (myJEServer[((string)(tPvName.Key))].PVAL).ToString();
                    }
                    sbyte sb = Convert.ToSByte(s, 10);
                    byte b = (byte)sb;
                    tString = b.ToString("X2");
                }
                else if ((string)(dataType[0]) == CommonConst.DT_DOUBLEARRAY || (string)(dataType[0]) == CommonConst.DT_JWAVE )    //only used when the array is a standard JTEXT wave array.
                {   //Random tRnd = new Random();
                    //Thread.Sleep(tRnd.Next(10, 200));
                    Console.WriteLine("Try Get PV: " + (string)(tPvName.Key));
                    //double[] s = (double [])(myJEClient.RecordVAL[(string)(tPvName.Key)]);   //join all the double values to a string, spliter is ";"
                    double[] s;
                    //if (myJEServer == null)
                    //{
                    //    s = myJEClient[(string)(tPvName.Key)].Get<double[]>(110);
                    //}
                    //else
                    //{
                    //    //use values on the server
                    //    //s = ((double[])(myJEServer[((string)(tPvName.Key))].VAL));
                    //    //use pv on server
                    //    s = ((double[])(myJEServer[((string)(tPvName.Key))].PVAL));
                    //}
                    try
                    {
                        s = ((double[])(myJEServer[((string)(tPvName.Key))].PVAL));
                    }
                    catch
                    {
                        s = myJEClient[(string)(tPvName.Key)].Get<double[]>(110);
                    }

                    double iwaveLen = s[0];
                    Console.WriteLine("Fill PV: " + (string)(tPvName.Key));
                    Console.WriteLine("PV Lenth : " + s.Length.ToString());
                    Console.WriteLine("WAVE LENTH: "+iwaveLen.ToString());
                    if (strFomat != "")
                    {
                        Console.WriteLine("With Format: " + strFomat);
                        tString = s[1].ToString(strFomat);
                        Console.WriteLine(s[1].ToString(strFomat));
                        for (int i = 2; i <= iwaveLen; i++)
                        {
                            tString += TEMP_SPLITER + s[i].ToString(strFomat);
                            Console.WriteLine(s[i].ToString(strFomat));
                        }
                    }
                    else
                    {
                        tString = s[1].ToString();
                        Console.WriteLine(s[1].ToString());
                        for (int i = 2; i <= iwaveLen; i++)
                        {
                            tString += TEMP_SPLITER + s[i].ToString();
                            Console.WriteLine(s[i].ToString());
                        }
                    }
                }
                else //just to string
                {
                    if (strFomat != "")
                    {
                        double s;
                        //if (myJEServer == null)
                        //{
                        //    s=(double)myJEClient.RecordVAL[(string)(tPvName.Key)];
                        //}
                        //else
                        //{
                        //    //use values on the server
                        //    //s = ((double[])(myJEServer[((string)(tPvName.Key))].VAL));
                        //    //use pv on server
                        //    s = ((double)(myJEServer[((string)(tPvName.Key))].PVAL));
                        //}
                        try
                        {
                            s = ((double)(myJEServer[((string)(tPvName.Key))].PVAL));
                        }
                        catch
                        {
                            s = (double)myJEClient.RecordVAL[(string)(tPvName.Key)];
                        }
                        tString = s.ToString(strFomat);
                        
                    }
                    else
                    {
                        //if (myJEServer == null)
                        //{
                        //    tString = myJEClient.RecordVAL[(string)(tPvName.Key)].ToString();
                        //}
                        //else
                        //{
                        //    //use values on the server
                        //    //s = ((double[])(myJEServer[((string)(tPvName.Key))].VAL));
                        //    //use pv on server
                        //    tString = ((myJEServer[((string)(tPvName.Key))].PVAL)).ToString();
                        //    Console.WriteLine("fill: " + tPvName.Key.ToString()+" = "+tString);
                        //}
                        try
                        {
                            tString = ((myJEServer[((string)(tPvName.Key))].PVAL)).ToString();
                        }
                        catch
                        {
                            tString = myJEClient.RecordVAL[(string)(tPvName.Key)].ToString();
                            Console.WriteLine("fill: " + tPvName.Key.ToString()+" = "+tString);
                        }
                        //tString = (myJEServer[((string)(tPvName.Key))].PVAL).ToString();
                    }
                }
                int j=0;
                for(j=0;j<msgArray.Length;j++)
                {
                    if(msgArray[j]==(PV_PREFIX + (string)(tPvName.Key)))
                    {
                        msgArray[j]=tString;
                        Console.WriteLine(tString);
                    }
                }
                //msg = msg.Replace(PV_PREFIX + (string)(tPvName.Key), tString);
            }
            msg = string.Join(TEMP_SPLITER, msgArray);
            Console.WriteLine(msg);
            return msg;

        }


        private void SendMessage(byte[] msg)
        {
            if (myEJServer != null)
            {
                //use server to send,not quite recommand
                myEJServer.SendFromServer(msg, EJServer.Reply_to.Non);
            }
            else
            {
                if (myEJClient.ClientStatus == 0)
                {
                    myEJClient.Msg_send(msg);
                }
            }
        }

        private void SendMessage(string msg)
        {
            if (myEJServer != null)
            {
                //use server to send,not quite recommand
                myEJServer.SendFromServer(msg, EJServer.Reply_to.Non);
            }
            else
            {
                if (myEJClient.ClientStatus == 0)
                {
                    myEJClient.Msg_send(msg);
                }
            }
        }

        private void ConsoleOut(string s)
        {
            if (isConsole==false )
                return;
            Console.WriteLine(s);

        }


    }
}
